/* **********************************************************************
 *
 *  ROLANDS & ASSOCIATES Corporation
 *  500 Sloat Avenue
 *  Monterey, CA 93940
 *  (831) 373-2025
 *
 *  Copyright (C) 2002, 2003 ROLANDS & ASSOCIATES Corporation. All rights reserved.
 *  Openmap is a trademark of BBN Technologies, A Verizon Company
 *
 *
 * **********************************************************************
 *
 * $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/layer/ScaleDisplayLayer.java,v $
 * $Revision: 1.9 $
 * $Date: 2005/12/09 21:09:08 $
 * $Author: dietrick $
 *
 * **********************************************************************
 */

package com.bbn.openmap.layer;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Properties;
import java.util.Vector;
import java.util.logging.Logger;

import javax.swing.ButtonGroup;
import javax.swing.JPanel;
import javax.swing.JRadioButton;

import com.bbn.openmap.omGraphics.DrawingAttributes;
import com.bbn.openmap.omGraphics.OMColor;
import com.bbn.openmap.omGraphics.OMGraphicList;
import com.bbn.openmap.omGraphics.OMLine;
import com.bbn.openmap.omGraphics.OMText;
import com.bbn.openmap.proj.Length;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.proj.coords.LatLonPoint;
import com.bbn.openmap.util.PropUtils;

/**
 * Layer objects are components which can be added to the MapBean to make a map.
 * <p>
 * Layers implement the ProjectionListener interface to listen for
 * ProjectionEvents. When the projection changes, they may need to re-fetch,
 * regenerate their graphics, and then repaint themselves into the new view.
 * <p>
 * <pre>
 * 
 * ### Layer used by the overview handler
 * scaleLayer.class=com.rolands.jtlsweb.map.layer.ScaleDisplayLayer
 * scaleLayer.prettyName=Scale
 * scaleLayer.lineColor=ff777777
 * scaleLayer.textColor=ff000000
 * scaleLayer.unitOfMeasure=nm
 * scaleLayer.locationXoffset=-10
 * scaleLayer.locationYoffset=-20
 * scaleLayer.width=150
 * scaleLayer.height=10
 * 
 * # unitOfMeasure - any com.bbn.openmap.proj.Length instance returned by Length.get(string).
 * # locationXoffset - offset in pixels from left/right, positive from left edge, negative from right edge
 * # locationYoffset - offset in pixels from top/bottom, positive from top edge, negative from bottom edge
 * # width - width of scale indicator bar in pixels
 * # height - height of scale indicator bar in pixels
 *
 * </pre>
 */
public class ScaleDisplayLayer extends OMGraphicHandlerLayer {

   public ScaleDisplayLayer() {
      super();
      setProjectionChangePolicy(new com.bbn.openmap.layer.policy.ListResetPCPolicy(this));
      setUnitOfMeasure(Length.KM.toString());
   }

   protected Logger logger = Logger.getLogger("com.bbn.openmap.layer.ScaleDisplayLayer");

   // Color variables for different line types
   protected java.awt.Color lineColor = null;
   protected java.awt.Color textColor = null;

   // Default colors to use, if not specified in the properties.
   protected String defaultLineColorString = "FFFFFF";
   protected String defaultTextColorString = "FFFFFF";
   protected String defaultUnitOfMeasureString = "km";
   protected int defaultLocationXoffset = -10;
   protected int defaultLocationYoffset = -10;
   protected int defaultWidth = 150;
   protected int defaultHeight = 10;

   // property text values
   public static final String UnitOfMeasureProperty = "unitOfMeasure";
   public static final String LocationXOffsetProperty = "locationXoffset";
   public static final String LocationYOffsetProperty = "locationYoffset";
   public static final String WidthProperty = "width";
   public static final String HeightProperty = "height";

   protected String unitOfMeasure = null;
   protected Length uom = Length.get(defaultUnitOfMeasureString);
   protected String uomAbbr = uom.getAbbr();
   protected int locationXoffset = defaultLocationXoffset;
   protected int locationYoffset = defaultLocationYoffset;
   protected int width = defaultWidth;
   protected int height = defaultHeight;

   protected DrawingAttributes dAttributes = DrawingAttributes.getDefaultClone();

   /**
    * Sets the properties for the <code>Layer</code>. This allows
    * <code>Layer</code> s to get a richer set of parameters than the
    * <code>setArgs</code> method.
    * 
    * @param prefix the token to prefix the property names
    * @param properties the <code>Properties</code> object
    */
   public void setProperties(String prefix, Properties properties) {
      super.setProperties(prefix, properties);
      prefix = PropUtils.getScopedPropertyPrefix(prefix);

      dAttributes.setProperties(prefix, properties);

      String unitOfMeasureString = properties.getProperty(prefix + UnitOfMeasureProperty);
      if (unitOfMeasureString != null) {
         setUnitOfMeasure(unitOfMeasureString);
      }

      locationXoffset = PropUtils.intFromProperties(properties, prefix + LocationXOffsetProperty, defaultLocationXoffset);

      locationYoffset = PropUtils.intFromProperties(properties, prefix + LocationYOffsetProperty, defaultLocationYoffset);

      width = PropUtils.intFromProperties(properties, prefix + WidthProperty, defaultWidth);

      height = PropUtils.intFromProperties(properties, prefix + HeightProperty, defaultHeight);
   }

   public Properties getProperties(Properties props) {
      props = super.getProperties(props);
      String prefix = PropUtils.getScopedPropertyPrefix(this);

      dAttributes.setProperties(props);

      props.put(prefix + LocationXOffsetProperty, Integer.toString(locationXoffset));
      props.put(prefix + LocationYOffsetProperty, Integer.toString(locationYoffset));
      props.put(prefix + WidthProperty, Integer.toString(width));
      props.put(prefix + HeightProperty, Integer.toString(height));

      props.put(prefix + UnitOfMeasureProperty, unitOfMeasure);

      return props;
   }

   public synchronized OMGraphicList prepare() {
      int w, h, left_x = 0, right_x = 0, lower_y = 0, upper_y = 0;
      Projection projection = getProjection();
      OMGraphicList graphics = new OMGraphicList();

      w = projection.getWidth();
      h = projection.getHeight();
      if (locationXoffset < 0) {
         left_x = w + locationXoffset - width;
         right_x = w + locationXoffset;
      } else if (locationXoffset >= 0) {
         left_x = locationXoffset;
         right_x = locationXoffset + width;
      }
      if (locationYoffset < 0) {
         upper_y = h + locationYoffset - height;
         lower_y = h + locationYoffset;
      } else if (locationYoffset >= 0) {
         upper_y = locationYoffset;
         lower_y = locationYoffset + height;
      }

      graphics.clear();

      OMLine line = new OMLine(left_x, lower_y, right_x, lower_y);
      dAttributes.setTo(line);
      graphics.add(line);

      line = new OMLine(left_x, lower_y, left_x, upper_y);
      dAttributes.setTo(line);
      graphics.add(line);

      line = new OMLine(right_x, lower_y, right_x, upper_y);
      dAttributes.setTo(line);
      graphics.add(line);

      /*
       * We need to use better coordinates to measure distance, like the same
       * pixel distance at the center of the map. There's a problem using the
       * lower right location, in that those distances decrease as you zoom out.
       */

      int y = h / 2;
      int x = w / 2;
      int xSide = (right_x - left_x) / 2;

      LatLonPoint loc1 = projection.inverse(x - xSide, y, new LatLonPoint.Double());
      LatLonPoint loc2 = projection.inverse(x + xSide, y, new LatLonPoint.Double());

      double dist = uom.fromRadians(loc1.distance(loc2));

      String outtext;
      if (dist < 1.0f) {
         outtext = String.format("%.3f %s", dist, uomAbbr);
      } else if (dist < 10.0f) {
         outtext = String.format("%.2f %s", dist, uomAbbr);
      } else if (dist < 100.0f) {
         outtext = String.format("%.1f %s", dist, uomAbbr);
      } else {
         outtext = String.format("%.0f %s", dist, uomAbbr);
      }

      OMText text = new OMText((left_x + right_x) / 2, lower_y - 3, "" + outtext, OMText.JUSTIFY_CENTER);

      Font font = text.getFont();
      text.setFont(font.deriveFont(font.getStyle(), font.getSize() + 4));

      dAttributes.setTo(text);
      text.setTextMatteColor((Color) dAttributes.getMattingPaint());
      text.setTextMatteStroke(new BasicStroke(5));
      text.setMattingPaint(OMColor.clear);
      graphics.add(text);
      graphics.generate(projection);

      return graphics;
   }

   /**
    * Getter for property unitOfMeasure.
    * 
    * @return Value of property unitOfMeasure.
    */
   public String getUnitOfMeasure() {
      return this.unitOfMeasure;
   }

   /**
    * Setter for property unitOfMeasure.
    * 
    * @param unitOfMeasure New value of property unitOfMeasure.
    * 
    * @throws PropertyVetoException
    */
   public void setUnitOfMeasure(String unitOfMeasure) {
      if (unitOfMeasure == null)
         unitOfMeasure = Length.KM.toString();
      this.unitOfMeasure = unitOfMeasure;

      // There is a bug in the Length.get() method that will not
      // return
      // the correct (or any value) for a requested uom.
      // This does not work:
      // uom = com.bbn.openmap.proj.Length.get(unitOfMeasure);

      // Therefore, The following code correctly obtains the proper
      // Length object.

        Length[] choices = Length.values();
        uom = null;
        for (int i = 0; i < choices.length; i++) {
            if (unitOfMeasure.equalsIgnoreCase(choices[i].toString())
                    || unitOfMeasure.equalsIgnoreCase(choices[i].getAbbr())) {
                uom = choices[i];
                break;
            }
        }

      // of no uom is found assign Kilometers as the default.
      if (uom == null)
         uom = Length.KM;

      uomAbbr = uom.getAbbr();

   }

   JPanel palettePanel;
   ButtonGroup uomButtonGroup;
   Vector<JRadioButton> buttons = new Vector<JRadioButton>();

   /** Creates the interface palette. */
   public java.awt.Component getGUI() {

      if (palettePanel == null) {

         logger.fine("creating palette.");

         palettePanel = new JPanel();
         uomButtonGroup = new ButtonGroup();

         palettePanel.setLayout(new javax.swing.BoxLayout(palettePanel, javax.swing.BoxLayout.Y_AXIS));
         palettePanel.setBorder(new javax.swing.border.TitledBorder("Unit Of Measure"));

         java.awt.event.ActionListener al = new ActionListener() {
            // We don't have to check for action commands or anything like that.
            // We know this listener is going to be added to JRadioButtons that
            // are labeled with abbreviations for length.
            public void actionPerformed(ActionEvent e) {
               JRadioButton jrb = (JRadioButton) e.getSource();
               setUnitOfMeasure(jrb.getText());
            }
         };

            for (Length lengthType : Length.values()) {
                JRadioButton jrb = new JRadioButton();
                jrb.setText(lengthType.getAbbr());
                jrb.setToolTipText(lengthType.toString());
                uomButtonGroup.add(jrb);
                palettePanel.add(jrb);

            jrb.addActionListener(al);

            jrb.setSelected(unitOfMeasure.equalsIgnoreCase(lengthType.getAbbr()));
            buttons.add(jrb);
         }

      } else {
         for (JRadioButton button : buttons) {
            button.setSelected(uom.getAbbr().equalsIgnoreCase(button.getText()));
         }
      }

      return palettePanel;
   }
}
